package ram;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

// Implementation of RAM class
//
// g++ (Ubuntu 4.4.1-4ubuntu9) 4.4.1 on
// Linux 2.6.31-23-generic 
//
// Editor:  tabstop=2, cols=80
//
// 5/21/11 - KS, added CAL instruction
// 5/11 - KS, hanged initialisation of RAM to use a given memory size.
// 5/11 - added MUL and JMZ, KS
// 5/27/12 - JL ported to java
//

import ram.Instruction.OPCODES;

public class RAM {
	
	// Memory array
	private ArrayList<Integer> memory;
	
	// Program as a list of instructions
	private ArrayList<Instruction> program;
	
	// Program counter
	private int pc;
	
	// Accumulator
	private int ac;
	
	// How much ram are we actually using?
	private int usedRamSize = 0;
	
	public RAM() {
		
		setPC(1);
		setAC(0);
		
		// Indexing for RAM memory starts at 1, so stick something irrelevant in at 0
		memory = new ArrayList<Integer>();
		memory.add(0);
		usedRamSize++;

		// indexing for RAM program starts at 1, so stick something irrelevant in at 0
		program = new ArrayList<Instruction>();
		program.add( new Instruction(OPCODES.ILL,0) );
	}
	
	// Initialize RAM with hardwired program and memory
	// pc is set to 1 and ac is set to 0
	public void init() {
		program.add( new Instruction( OPCODES.LDA, 3 ));
		program.add( new Instruction( OPCODES.SUB, 4 ));
		program.add( new Instruction( OPCODES.JMZ, 7 ));
		program.add( new Instruction( OPCODES.LDA, 1 ));
		program.add( new Instruction( OPCODES.STA, 5 ));
		program.add( new Instruction( OPCODES.HLT) );
		program.add( new Instruction( OPCODES.LDA, 2 ));
		program.add( new Instruction( OPCODES.STA, 5 ));
		program.add( new Instruction( OPCODES.HLT) );

		memory.add( 0 );
		memory.add( 1 );
		memory.add( 2 );
		memory.add( 1 );
		memory.add( 3 );
	}
	
	// Initialize RAM with program in file with the name pInput
	// and initial memory configuration in the file with name mInput
	// pc is set to 1 and ac is set to 0.  programSize is set to the number
	// of instructions read.
	public void init( BufferedReader pFile, BufferedReader mFile, int req_size ) throws IOException {		

		// Make memory as big as it needs to be
		while( memory.size() < req_size ) {
			memory.add(0);
		}

		// Initialize it with what's in the mFile
		String line = mFile.readLine();
		while( line != null ) {
			StringTokenizer strtok = new StringTokenizer(line);
			try {
				int index = Integer.parseInt( strtok.nextToken() );
				int value = Integer.parseInt( strtok.nextToken() );
				memory.set(index, value);
				if( index > this.usedRamSize ) {
					usedRamSize = index;
				}
			} catch( Exception e ) {
				System.err.println( "Error initializing memory of size " + req_size );
				System.err.println( "Memory line was: " + line );
				System.exit(1);
			}
			line = mFile.readLine();
		}

		////   Initialize program   //////////////////////
		line = pFile.readLine();
		// Since we already added a dummy value at index 0 of the program, we
		// can just add in instructions in order
		while( line != null ) {
			
			// Comments start with ;, also ignore blank lines
			if( line.charAt(0) != ';' && line.trim().length() > 0 ) {
				
				// Get whatever is before the ; that indicates a comment
				int commentStartsAt = line.indexOf(';');
				String instruction = commentStartsAt == -1 ? line : line.substring(0,commentStartsAt);
				StringTokenizer strtok = new StringTokenizer(instruction);
				
				String code = null;
				String operand = null;
				try {
					code = strtok.nextToken();
					operand = strtok.nextToken();
				} catch( NoSuchElementException e ) {
					// Oh well, error-checking will be done later
				}
				Instruction inst = new Instruction(code,operand);
				
				// Is it valid?
				if( !inst.isValid() ) {
					System.err.println( "Encountered an invalid program statement: " + line );
					System.exit(1);
				}
					
				program.add( inst );
			}
			
			line = pFile.readLine();
		}
	}
	
	// simulate execution of RAM with given program and memory configuration.
	// Notes:
	//    1. Program may not terminate (if HLT is not executed)
	//    2. Currently no error checking is performed.  Checks for valid program 
	//       and memory addresses and illegal opcodes should be provided.
	public void execute() {
		
		int x;
		OPCODES op;
		boolean halted = false;

			// start at first instruction
		pc = 1 ;

		try {
			while (!halted) {
				op = program.get(pc).getOpcode();
				switch (op) {
				case LDA:
					x = program.get(pc).getOperand();
					ac = memory.get(x);
					pc++;
					break;
	
				case LDI:
					x = program.get(pc).getOperand();
					ac = memory.get(memory.get(x));
					pc++;
					break;
	
				case STA:
					x = program.get(pc).getOperand();
					memory.set(x, ac);
					pc++;
					break;
	
				case STI:
					x = program.get(pc).getOperand();
					memory.set(memory.get(x),ac);
					pc++;
					break;
	
				case ADD:
					x = program.get(pc).getOperand();
					ac = ac + memory.get(x);
					pc++;
					break;
	
				case SUB:
					x = program.get(pc).getOperand();
					ac = ac - memory.get(x);
					pc++;
					break;
	
				case MUL:
					x = program.get(pc).getOperand();
					ac = ac * memory.get(x);
					pc++;
					break;
	
				case JMP:
					x = program.get(pc).getOperand();
					pc = x;
					break;
	
				case JMI:
					x = program.get(pc).getOperand();
					pc = memory.get(x);
					break;
	
				case JMZ:
					x = program.get(pc).getOperand();
					if (ac == 0)
						pc = x;
					else
						pc++;
					break;
	
				case JMN:
					x = program.get(pc).getOperand();
					if (ac < 0)
						pc = x;
					else
						pc++;
					break;
	
				case CAL :
						// NOTE  it is assumed that SP is in memory[0],
						//  that it points to the *last* location in the current (new)
						//  frame, and that the return address is stored there.
					x = program.get(pc).getOperand();
					memory.set( memory.get(ram.Instruction.SP), pc+1);
					pc = x ;	// jump to the function
					break ;
	
				case HLT:
					halted = true ;
					break;
	
				default :  // shouldn't be here
					halted = true;
					System.err.println( "Error: Got opcode " + op + " at pc=" + pc);
				}	// switch
			}	// while not halted
		} catch( Exception e ) {
			e.printStackTrace();
			dump();
		}
	}

	// Dump memory contents
	public void dump() {
		
		System.out.println ("RAM Memory Contents" );
		
		// We used to be printing out just the usedRamSize. Problem is, you're
		// going to miss seeing things that are set in the activation records.
		// Since the main program is a procedure now, you won't see any values
		// that are being set.
		for( int i=0; i < memory.size() && i <= usedRamSize; i++ ) {
			System.out.println( "memory[" + i + "] = " + memory.get(i) );
		}
		
		// So we print extra non-0 values here....
		int endOfNonZeroes = 0;
		for( int i = 0; i < memory.size(); i++ ) {
			if( memory.get(i) > 0 ) {
				endOfNonZeroes = i;
			}
		}
		for( int i = usedRamSize+1; i <= endOfNonZeroes; i++ ) {
			System.out.println( "memory[" + i + "] = " + memory.get(i) );
		}
	}
	
	private void setPC( int value ) {
		pc = value;
	}
	
	protected int getPC() {
		return pc;
	}
	
	protected int getAC() {
		return ac;
	}
	
	private void setAC( int value ) {
		ac = value;
	}
}
